/*
 ------------------------------------------------------------------------------
 Copyright (C) 2013 Eternal Games.

 This file is part of the EternalTech source code.

 The EternalTech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The EternalTech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the EternalTech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// cl_send.cpp - sends packets to the server
//


#include "client.h"


/*
 ==================
 CL_WriteCommands
 ==================
*/
static void CL_WriteCommands (msg_t *msg){

	int		i;

	// Write any unackknowledged reliable commands
	for (i = clc.reliableCommandAcknowledge + 1; i <= clc.reliableCommandSequence; i++){
		MSG_WriteByte(msg, CLC_COMMAND);
		MSG_WriteLong(msg, i);
		MSG_WriteString(msg, clc.reliableCommandList[i & (MAX_RELIABLE_COMMANDS - 1)]);
	}
}

/*
 ==================
 CL_WriteMove
 ==================
*/
static void CL_WriteMove (msg_t *msg){

	userCmd_t	nullUserCmd;
	userCmd_t	*oldUserCmd, *newUserCmd;
	int			index, count;
	int			i;

	if (cls.state < CS_PRIMED)
		return;

	// We want to send all the user commands that were generated in the last
	// few packets, so even if a couple packets are dropped in a row, all the
	// user commands will make it to the server
	index = (clc.netChan.outgoingSequence - 1 - cl_dupPackets->integerValue) & PACKET_MASK;

	count = cl.userCmdNumber - cl.packets[index].userCmdNumber;
	if (count < 1)
		return;

	if (count > USERCMD_BACKUP)
		count = USERCMD_BACKUP;

	// Begin a client move
	if (!cl_deltaCompress->integerValue || !cl.snapshotValid || cl.snapshotSequence != clc.serverMessageSequence || clc.demoWaiting)
		MSG_WriteByte(msg, CLC_MOVENODELTA);
	else
		MSG_WriteByte(msg, CLC_MOVE);

	// Write the user command count
	MSG_WriteByte(msg, count);

	// Write all the user commands
	oldUserCmd = &nullUserCmd;
	Mem_Fill(oldUserCmd, 0, sizeof(userCmd_t));

	for (i = 0; i < count; i++){
		index = (cl.userCmdNumber - count + i + 1) & USERCMD_MASK;
		newUserCmd = &cl.userCmds[index];

		if (newUserCmd->time - oldUserCmd->time < 256){
			MSG_WriteBits(msg, 1, 1);
			MSG_WriteBits(msg, newUserCmd->time - oldUserCmd->time, 8);
		}
		else {
			MSG_WriteBits(msg, 0, 1);
			MSG_WriteBits(msg, newUserCmd->time, 32);
		}

		MSG_WriteDeltaByte(msg, oldUserCmd->flags, newUserCmd->flags);
		MSG_WriteDeltaLong(msg, oldUserCmd->buttons, newUserCmd->buttons);
		MSG_WriteDeltaChar(msg, oldUserCmd->forwardMove, newUserCmd->forwardMove);
		MSG_WriteDeltaChar(msg, oldUserCmd->sideMove, newUserCmd->sideMove);
		MSG_WriteDeltaChar(msg, oldUserCmd->upMove, newUserCmd->upMove);
		MSG_WriteDeltaShort(msg, oldUserCmd->angles[0], newUserCmd->angles[0]);
		MSG_WriteDeltaShort(msg, oldUserCmd->angles[1], newUserCmd->angles[1]);
		MSG_WriteDeltaShort(msg, oldUserCmd->angles[2], newUserCmd->angles[2]);

		oldUserCmd = newUserCmd;
	}

	// Align to the next byte boundary
	MSG_WriteAlign(msg);
}

/*
 ==================
 
 ==================
*/
static void CL_WriteVoice (msg_t *msg){

}


// ============================================================================


/*
 ==================
 CL_ReadyToSendPacket

 Returns false if we are over the max packets limit and should choke back the
 bandwidth a bit by not sending a packet this frame. All the user commands will
 still get delivered in the next packet, but saving a header and getting more
 compression will reduce total bandwidth.
 ==================
*/
static bool CL_ReadyToSendPacket (){

	int		packetTime;

	// If we are downloading, only send a packet every 50 milliseconds
	if (cls.state == CS_CONNECTED && clc.downloadFile){
		if (cls.frameTime - clc.lastSentTime < 50)
			return false;

		return true;
	}

	// If we are connecting or loading a level, only send a packet per second
	if (cls.state < CS_PRIMED){
		if (cls.frameTime - clc.lastSentTime < 1000)
			return false;

		return true;
	}

	// Send every frame if the server is on the local network
	if (NET_IsLANAddress(clc.netChan.remoteAddress))
		return true;

	// Check for exceeding the max packets limit
	packetTime = 1000 / cl_maxPackets->integerValue;

	if (cls.frameTime - clc.lastSentTime < packetTime)
		return false;

	return true;
}

/*
 ==================
 CL_SendPacket

 Creates and sends a packet to the server, including the reliable commands, the
 user commands, and any voice data
 ==================
*/
void CL_SendPacket (){

	byte	data[MAX_MSG_SIZE];
	msg_t	msg;
	int		index;

	if (cls.state < CS_CONNECTED || clc.demoPlaying)
		return;

	// Initialize the message buffer
	MSG_Init(&msg, data, sizeof(data), false);

	// Write the server id so the server can tell if this is from the current
	// server state
	MSG_WriteLong(&msg, cl.serverId);

	// Write the last message sequence we received, which can be used for delta
	// compression
	MSG_WriteLong(&msg, clc.serverMessageSequence);

	// Write the last reliable command we received
	MSG_WriteLong(&msg, clc.serverCommandSequence);

	// Write reliable commands if needed
	CL_WriteCommands(&msg);

	// Write user commands if needed
	CL_WriteMove(&msg);

	// Write voice data if needed
	CL_WriteVoice(&msg);

	// Write the EOF
	MSG_WriteByte(&msg, CLC_EOF);

	// Record information about this packet
	index = clc.netChan.outgoingSequence & PACKET_MASK;

	cl.packets[index].userCmdNumber = cl.userCmdNumber;

	// Deliver the message
	clc.lastSentTime = cls.frameTime;

	NetChan_Transmit(&clc.netChan, cls.frameTime, &msg);

	// Clients never really should have messages large enough to fragment, but
	// in case they do, fire them all off at once
	if (clc.netChan.outgoingFragments){
		Com_DPrintf(S_COLOR_YELLOW "Sending unsent client message fragments\n");

		NetChan_TransmitAllFragments(&clc.netChan, cls.frameTime);
	}
}

/*
 ==================
 CL_SendCommands
 ==================
*/
void CL_SendCommands (){

	if (cls.state < CS_CONNECTED || clc.demoPlaying)
		return;

	// Build a user command
	CL_BuildUserCmd();

	// Don't send a packet if the last packet was sent too recently
	if (!CL_ReadyToSendPacket())
		return;

	// Send a packet
	CL_SendPacket();
}